#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>


//数组指针变量
/*
* 数组指针变量是指针变量
* 存放的是数组的地址，能够指向数组的指针变量
*/
//int main()
//{
//	int a = 10;
//	int* pa = &a;//整型指针
//
//	char ch = 'w';
//	char* pc = &ch;//字符指针
//
//	int arr[10] = { 0 };
//	int (*p)[10] = &arr;//取出的是数组的地址
//
//	//arr -- 数组首元素的地址
//
//	int* p1[10]; //p1是指针数组 -- 存放的是指针的数组
//	int (*p2)[10]; //p2是数组指针 -- 存放的是数组的指针，该指针指向数组,且该数组的元素是int类型的
//
//	return 0;
//}

/* int (*p)[10]
* 解释：p先和*结合，说明p是一个指针变量，然后种指针指向的是一个大小为10个整型（int）的数组。
* 所以p是一个指针，指向一个数组，叫数组指针
* 这里需要注意[]的优先级是要高于*的，所以必须要加上（）来保证p先和*结合
*/

//int main()
//{
//	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//	int* p = arr;//取出数组首元素的地址
//	int i = 0;
//	for (i = 0; i < 10; i++)
//	{
//		printf("%d ", *p);
//		p++;
//	}
//	return 0;
//}

//不好的示范
//int main()
//{
//	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//	int (*p)[10] = &arr;//取出整个数组的地址
//	//& 和 * 是一对的
//	//*&arr == arr
//	int i = 0;
//	for (i = 0; i < 10; i++)
//	{
//		printf("%d ", *((*p) + i)); //(*p)[i]
//	}
//	return 0;
//}

//int main()
//{
//	int arr[10] = { 0 };
//	int (*p)[10] = &arr;
//
//	return 0;
//}


//再次补充强调
//1.sizeof + 数组名 这里的数组名表示整个数组，计算的是整个数组的大小，单位是字节
//2.&数组名 这里的数组名表示的是整个数组，取出的是整个数组的地址（整个数组的地址和数组首元素的地址是有区别的）
//除以上两种情况，arr均表示数组首元素的地址

//int arr[10] = { 0 };
//int (*p)   [10] = &arr;
//|     |     |
//|     |     | 
//|     |    p指向数组的元素个数
//|     |    
//|    p是数组指针变量名
//|
//p指向的数组的元素类型是int
// 
// 
// 

//二维数组传参的本质
// 
//




//1.

//void print(int arr[3][5], int r, int c)
//{
//	int i = 0;
//	for (i = 0; i < r; i++)
//	{
//		int j = 0;
//		for (j = 0; j < c; j++)
//		{
//			printf("%d ", arr[i][j]);
//		}
//		printf("\n");
//	}
//}



//2.
//二维数组传参本质上也就是传递了地址，传递的是第一行这个一维数组的地址
//那么形参也是可以写成指针形式的
//
//我们再次来理解一下二维数组，二维数组可以看作是每个元素都是一维数组的数组，也就是
//二维数组的每个元素都是一个一维数组，那么二维数组的首元素就是第一行，就是一个一维数组
//放在这个例子上，如果实参传过arr之后，即把二维数组的首元素是地址，也就是一个一维数组的地址传了过来
//所以用int (*arr)[5] 这个数组指针来接收
//void print(int (*arr)[5], int r, int c)
//{
//	int i = 0;
//	for (i = 0; i < r; i++)
//	{
//		int j = 0;
//		for (j = 0; j < c; j++)
//		{
//			printf("%d ",*(*(arr + i) + j));
//		}
//		printf("\n");
//	}
//}
//
//
//
//int main()
//{
//	int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
//	print(arr, 3, 5);
//	return 0;
//}
//
//补充：
//*(p + i) = arr[i]
//总结：二维数组传参，形参的部分可以写成数组，也可以写成指针的形式

//int main()
//{
//	int a = 10;
//	int* pa = &a;//pa存放a的地址，但pa也是一个变量，它也有地址
//	int** ppa = &pa;//二级指针存放指针变量的地址，即ppa存放pa的地址
//	return 0;
//}


//函数指针变量
//int Add(int x, int y)
//{
//	return x + y;
//}
//
//char* test(char c, char d)
//{
//	;
//}
//int main()
//{
//	printf("&Add = %p\n", &Add);
//	printf("Add  = %p\n", Add);
//	//&数组名 -- 表示取出数组的地址
//	//数组名  -- 表示数组首元素的地址
//	//两个地址虽然数组一样，但本质上是不一样的
//
//	//&函数名  和  函数名都是函数的地址，没有区别
//
//	int arr[10] = { 0 };
//	int (*pa)[10] = &arr;//数组指针变量
//
//	int (*pf)(int, int) = &Add;//pf是专门用来存放函数的地址的，pf就是函数指针变量
//	char* (*pc)(char, char) = &test;
//	return 0;
//}

//函数是有地址的，函数名就是函数的地址，当然也可以通过&函数名的方式获得函数的地址
//要将函数的地址存放起来，就得创建函数指针变量，函数指针变量的写法其实和数组指针变量类似

//void test()
//{
//	printf("123\n");
//}
//void (*pf1)() = &test;
//void (*pf2)() = test;
//
//int Add(int x, int y)
//{
//	return x + y;
//}
//int (*pf3)(int, int) = Add;
//int (*pf4)(int x, int y) = &Add;
//pf4中的x和y协商和省略都是可以的


//  int (*pf3) (int, int)
//   |    |      |
//   |    |      | 
//   |    |      |
//   |    |   pf3指向函数的参数类型和个数的交代
//   |    |
//   |  函数指针变量名
//   |
// pf3指向的函数的返回类型
//
//int (*) (int, int) -- 表述pf3函数指针变量的类型
//出去函数名剩下的就是类型


//函数指针变量的使用
//int Add(int x, int y)
//{
//	return x + y;
//}
//
//int main()
//{
//	int (*pf)(int, int) = Add;
//	
//	int c = Add(2, 3);//函数名调用
//	printf("%d\n", c);
//
//	int d = (*pf)(3, 4);//函数指针调用
//	printf("%d\n", d);
//
//	int e = pf(4, 5); //函数指针调用
//	printf("%d\n", e);
//
//	return 0;
//}

//两段有趣的代码


//1.
//int main()
//{
//	( * ( void (*) () ) 0 )();
//	//void(*)() 函数指针类型
//	// ( void(*)() )强制类型转换
//	// ( void(*)() ) 0 --将0强制类型转化为void(*)()的函数指针类型
//	// //这就意味着我们假设0地址处放着无参，且返回类型会void的函数
//	//最终调用0地址处放着的这个函数
//	return 0;
//}


//2.
//void (* signal( int, void(*)(int) ) )(int) 函数声明
//
//有一个函数是 signal(int, void(*)(int) )
//这个函数的返回类型是void (*) (int)


//typedef关键字
//type类型
//def定义
//类型重定义
//typedef是用来类型重命名的，可以将复杂的类型加丹化

//例如，unsigned int 写起来不方便，如果能写成unit就方便多了,则，我们可以写成
//typedef unsigned int uint;
//将unsigned int 重命名为uint

//如果是指针类型，能否重命名呢？其实也是可以的，比如，将int* 重命名为ptr_t;
//typedef int* ptr_t;
//int* pa = &a;
//ptr_t pb = &b;
//int a = 10;
//int b = 20;

//但是对于数组指针和函数指针稍微有点区别
//比如有数组指针类型 int(*)[5] 重命名为parr_t
//typedef int(*parr_t)[5]; //注意，新的类型一定要写在*的右边

//函数指针类型也是一样的 比如，将void(*)(int) 类型重命名为pf_t;
//typedef void(*pf_t)(int);

//则代码二
//void (* signal( int, void(*)(int) ) )(int) 函数声明
//可以简化为
//typedef void(*pun_t)(int);
//pun_t signal(int, pun_t);

//这里就会想到，那我用define岂不是和typedef一个效果吗？？？
//define无法实现类型的定义
//举例

//函数指针数组
//数组是一个存放相同类型数据的储存空间


//指针数组 int *arr[10]  //数组的每个元素是int，arr先和[]结合，所以是本质是数组
//要把函数的地址存在一个数组中，那这个数组就叫做函数指针数组
//int (*parr1[3])()  --  parr1先和[]结合，说明parr1是数组，数组的内容是
//int(*)()类型的函数指针

//实现一个计算器


//void menu()
//{
//	printf("******  1.Add   2. Sub    ********\n");
//	printf("******  3.Mul   4. Div    ********\n");
//	printf("******    0.  exit        ********\n");
//	printf("**********************************\n");
//}
//
//int Add(int x, int y)
//{
//	return x + y;
//}
//
//int Sub(int x, int y)
//{
//	return x - y;
//}
//
//int Mul(int x, int y)
//{
//	return x * y;
//}
//
//int Div(int x, int y)
//{
//	if (y == 0)
//	{
//		printf("输入错误\n");
//		return !0;
//	}
//	return x / y;
//}
//
//int main()
//{
//	int input = 0;
//	int x = 0;
//	int y = 0;
//	int z = 0;
//	do
//	{
//		menu();
//		printf("请输入：>\n");
//		scanf("%d", &input);
//		switch (input)
//		{
//		case 1:
//			printf("请输入两个操作数：\n");
//			scanf("%d %d", &x, &y);
//			z = Add(x, y);
//			printf("%d\n", z);
//			break;
//		case 2 :
//			printf("请输入两个操作数：\n");
//			scanf("%d %d", &x, &y);
//			z = Sub(x, y);
//			printf("%d\n", z);
//			break;
//		case 3 :
//			printf("请输入两个操作数：\n");
//			scanf("%d %d", &x, &y);
//			z = Mul(x, y);
//			printf("%d\n", z);
//			break;
//		case 4 :
//			printf("请输入两个操作数：\n");
//			scanf("%d %d", &x, &y);
//			z = Div(x, y);
//			printf("%d\n", z);
//			break;
//		case 0:
//			printf("退出游戏\n");
//			break;
//		default:
//			printf("选择错误，请重新选择\n");
//			break;
//		}
//	} while (input);
//	return 0;
//}



//用函数指针数组实现
//
//int Add(int x, int y)
//{
//	return x + y;
//}
//
//int Sub(int x, int y)
//{
//	return x - y;
//}
//
//int Mul(int x, int y)
//{
//	return x * y;
//}
//
//int Div(int x, int y)
//{
//	if (y == 0)
//	{
//		printf("输入错误\n");
//		return !0;
//	}
//	return x / y;
//}

//int main()
//{
//	int (*pf1)(int, int) = Add;//pf1是函数指针变量
//
//	int (*pfarr[4])(int, int) = { Add, Sub, Mul, Div };//pfarr是函数指针数组
//
//	//计算8 4 的加减乘除
//	int i = 0;
//	for (i = 0; i < 4; i++)
//	{
//		int r = pfarr[i](8, 4);
//		printf("%d\n", r);
//	}
//	return 0;
//}


//转移表 即使用函数指针数组的方法
//int main()
//{
//	int input = 0;
//	int x = 0;
//	int y = 0;
//	int z = 0;
//	int (*pfArr[5])(int, int) = { 0 , Add, Sub, Mul, Div };
//	 //                           0    1    2     3    4
//	do
//	{
//		menu();
//		printf("请选择\n");
//		scanf("%d", &input);
//
//		if (input >= 1 && input <= 4)
//		{
//			printf("请选择两个操作数：\n");
//			scanf("%d %d", &x, &y);
//			z = pfArr[input](x, y); //函数指针数组的使用
//			printf("%d\n", z);
//		}
//		else if (input == 0)
//		{
//			printf("退出计算器\n");
//		}
//		else
//		{
//			printf("选择错误，请重新选择\n");
//		}
//
//	} while (input);
//	return 0;
//}

//回调函数
//想要实现的功能是：
//1.想把重复的代码实现成函数
//2.但是这个函数又需要完成不同的任务


void menu()
{
	printf("******  1.Add   2. Sub    ********\n");
	printf("******  3.Mul   4. Div    ********\n");
	printf("******    0.  exit        ********\n");
	printf("**********************************\n");
}


/*********************回调函数**********************/
int Add(int x, int y)
{
	return x + y;
}

int Sub(int x, int y)
{
	return x - y;
}

int Mul(int x, int y)
{
	return x * y;
}

int Div(int x, int y)
{
	if (y == 0)
	{
		printf("输入错误\n");
		return !0;
	}
	return x / y;
}

/*************************中间商**************************/
void calc(int (*pf)(int, int)) //通过这个函数指针，来进行回调函数
{
	int a = 0;
	int b = 0;
	int c = 0;
	printf("请输入两个操作数\n");
	scanf("%d %d", &a, &b);
	c = pf(a, b);
	printf("%d\n", c);
}

/************主调函数*******************/
int main()

{
	int x = 0;
	int y = 0;
	int z = 0;
	int input = 0;

	do
	{
		menu();
		printf("请选择\n");
		scanf("%d", &input);
		switch (input)
		{
		case 1 :
			calc(Add);
			break;
		case 2:
			calc(Sub);
			break;
		case 3:
			calc(Mul);
			break;
		case 4:
			calc(Div);
			break;
		case 0:
			printf("退出计算器\n");
			break;
		default:
			printf("输入错误，请重新输入\n");
			break;
		}
	} while (input);
	
	return 0;
}